在開始今天的內容前,猶豫了很久要先講『怎麼用c++寫一個簡單的訂閱/發布者?』或是『Launch是什麼?要怎麼使用?』,同時反思著自己的學習歷程,自己曾走過得那些歪路,是不是可以被別人的學習心得導正?讓有興趣的人可以避免掉這些多餘的學習成本...應該有幫助?
最後決定先來把Launch介紹完~
在ROS1就有著Launch 的啟動機制,也就是能以XML格式的文件來一次啟動多個節點或設定參數。
ROS1的指令是長這樣的:
roslaunch <package_name> <file.launch>
聰明的你應該可以猜到ROS2版本長怎麼樣,登愣~沒錯
ros2 launch <package_name> <file.launch> #啟動package中的launch文件
ros2 launch <path_to_launch_file> #啟動指定路徑的launch文件
ros2 launch <package_name> <launch_file_name> key:=value #啟動package中的launch文件,並帶入參數
ros2 launch <path_to_launch_file> key:=value #啟動指定路徑的launch文件,並帶入參數
之後也會提到參數的部份~
經過幾天的ROS2介紹,有先接觸過ROS1的人應該有發現兩者指令的改動規則
rostopic echo ...
rosnode info ...
rosbag record ...
roslaunch ...
變成
ros2 topic echo ...
ros2 node info ...
ros2 bag record ...
ros2 launch ...
除了指令的不同外,Launch文件所支援的語法:
ROS1只能以XML格式編寫,ROS2則可以依照個人使用習慣選擇Python、XML、或YAML。
ROS1的使用者可以方便的在ROS2延用XML格式,但要注意還是有一些不太一樣的地方~
不過聽說Python比較靈活一點,也是ROS2官方默認的語法。
官方提供的完整版Python版本範例:
# example_launch.py
import os
from ament_index_python import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import GroupAction
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch.substitutions import TextSubstitution
from launch_ros.actions import Node
from launch_ros.actions import PushRosNamespace
from launch_xml.launch_description_sources import XMLLaunchDescriptionSource
from launch_yaml.launch_description_sources import YAMLLaunchDescriptionSource
def generate_launch_description():
# args that can be set from the command line or a default will be used
background_r_launch_arg = DeclareLaunchArgument(
"background_r", default_value=TextSubstitution(text="0")
)
background_g_launch_arg = DeclareLaunchArgument(
"background_g", default_value=TextSubstitution(text="255")
)
background_b_launch_arg = DeclareLaunchArgument(
"background_b", default_value=TextSubstitution(text="0")
)
chatter_py_ns_launch_arg = DeclareLaunchArgument(
"chatter_py_ns", default_value=TextSubstitution(text="chatter/py/ns")
)
chatter_xml_ns_launch_arg = DeclareLaunchArgument(
"chatter_xml_ns", default_value=TextSubstitution(text="chatter/xml/ns")
)
chatter_yaml_ns_launch_arg = DeclareLaunchArgument(
"chatter_yaml_ns", default_value=TextSubstitution(text="chatter/yaml/ns")
)
# include another launch file
launch_include = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(
get_package_share_directory('demo_nodes_cpp'),
'launch/topics/talker_listener_launch.py'))
)
# include a Python launch file in the chatter_py_ns namespace
launch_py_include_with_namespace = GroupAction(
actions=[
# push_ros_namespace to set namespace of included nodes
PushRosNamespace('chatter_py_ns'),
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(
get_package_share_directory('demo_nodes_cpp'),
'launch/topics/talker_listener_launch.py'))
),
]
)
# include a xml launch file in the chatter_xml_ns namespace
launch_xml_include_with_namespace = GroupAction(
actions=[
# push_ros_namespace to set namespace of included nodes
PushRosNamespace('chatter_xml_ns'),
IncludeLaunchDescription(
XMLLaunchDescriptionSource(
os.path.join(
get_package_share_directory('demo_nodes_cpp'),
'launch/topics/talker_listener_launch.xml'))
),
]
)
# include a yaml launch file in the chatter_yaml_ns namespace
launch_yaml_include_with_namespace = GroupAction(
actions=[
# push_ros_namespace to set namespace of included nodes
PushRosNamespace('chatter_yaml_ns'),
IncludeLaunchDescription(
YAMLLaunchDescriptionSource(
os.path.join(
get_package_share_directory('demo_nodes_cpp'),
'launch/topics/talker_listener_launch.yaml'))
),
]
)
# start a turtlesim_node in the turtlesim1 namespace
turtlesim_node = Node(
package='turtlesim',
namespace='turtlesim1',
executable='turtlesim_node',
name='sim'
)
# start another turtlesim_node in the turtlesim2 namespace
# and use args to set parameters
turtlesim_node_with_parameters = Node(
package='turtlesim',
namespace='turtlesim2',
executable='turtlesim_node',
name='sim',
parameters=[{
"background_r": LaunchConfiguration('background_r'),
"background_g": LaunchConfiguration('background_g'),
"background_b": LaunchConfiguration('background_b'),
}]
)
# perform remap so both turtles listen to the same command topic
forward_turtlesim_commands_to_second_turtlesim_node = Node(
package='turtlesim',
executable='mimic',
name='mimic',
remappings=[
('/input/pose', '/turtlesim1/turtle1/pose'),
('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
]
)
return LaunchDescription([
background_r_launch_arg,
background_g_launch_arg,
background_b_launch_arg,
chatter_py_ns_launch_arg,
chatter_xml_ns_launch_arg,
chatter_yaml_ns_launch_arg,
launch_include,
launch_py_include_with_namespace,
launch_xml_include_with_namespace,
launch_yaml_include_with_namespace,
turtlesim_node,
turtlesim_node_with_parameters,
forward_turtlesim_commands_to_second_turtlesim_node,
])
看起來是不是落落長、要頭昏眼花了呢~~我來整理一下主要架構。
上面包含了:
簡化之後的必要架構其實滿簡單的
# example_launch.py
import os
from ament_index_python import get_package_share_directory
# 1. 依照需求import需要的函式庫,不一定每個都有用到
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import GroupAction
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch.substitutions import TextSubstitution
from launch_ros.actions import Node
from launch_ros.actions import PushRosNamespace
from launch_xml.launch_description_sources import XMLLaunchDescriptionSource
from launch_yaml.launch_description_sources import YAMLLaunchDescriptionSource
def generate_launch_description():
# 2.參數預設值的設定
background_r_launch_arg = DeclareLaunchArgument(
"background_r", default_value=TextSubstitution(text="0")
)
# 3.包含其他launch文件,或使用namespace
launch_include = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(
get_package_share_directory('package_name'),
'launch/launch_name.py'))
)
launch_include_with_namespace = GroupAction(
actions=[
PushRosNamespace('namespace_name'),
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(
get_package_share_directory('package_name'),
'launch/launch_name.py'))
),
]
)
# 4.啟動節點並添加參數或remapping(以turtlesim為例)
turtlesim_node = Node(
package='turtlesim',
namespace='turtlesim1',
executable='turtlesim_node',
name='sim'
)
turtlesim_node_with_parameters = Node(
package='turtlesim',
namespace='turtlesim2',
executable='turtlesim_node',
name='sim',
parameters=[{
"background_r": LaunchConfiguration('background_r'),
# ...
# 這裡的參數在 2. 有設定,我這邊省篇幅就拿掉了
# 一個node可以用的參數可以查詢檢查 未來會講到
}]
)
forward_turtlesim_commands_to_second_turtlesim_node = Node(
package='turtlesim',
executable='mimic',
name='mimic',
remappings=[
('/input/pose', '/turtlesim1/turtle1/pose'),
('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
]
)
# 把上面的那些都return後才能被啟動
return LaunchDescription([
background_r_launch_arg,
launch_include,
launch_include_with_namespace,
turtlesim_node,
turtlesim_node_with_parameters,
forward_turtlesim_commands_to_second_turtlesim_node,
])
我自己也有些使用經驗~明日待續!